<?php
class SP_Common_Page
{
	const REPLACE_CODE = 1;
	const REDIRECT_CODE = 2;
	
	private $part_content = array(); //отрендереный хтмл части 
	private $part_controller_name='part.controller.php';
	private $module_controller_name='module.controller.php';
	private $modules_route='routes.php';
	private $module_config_file='_config.php';
	private $module; //имя текущего модуля
	private $action; //имя текущего экшена
	private $route_vars = array(); //данные из uri
	private $controller_vars = array(); //данные контроллера
	private $route_to_go;
	private $cycle_prevent;
	private $content; //отрендереный хтмл контента
	private $page_content; //отрендереный хтмл страницы 
	private $template; //имя шаблона
	private $template_vars = array(); //переменные используемые в шаблоне
	private $use_template=true;
	private $css=array(); 
	private $js=array();
	private $css_dir='css';
	private $js_dir='js';
	private $slots=array();
	private $replaced_from;
	private $current_route_name;
	private $replaced_from_route_name;
	private $is_home_page;
	var $user;	//объект пользователя можно использовать в шаблоне, $this->user
	var $app;	//ссылка на объект приложения	
	
	public function __construct(&$app)
	{
		$this->app=$app;
		require_once(SP_Config_Configurator::getIncludeDir().$app->getConfigsDir().$this->modules_route);//инициализация маршрутов
		if(SP_Config_Configurator::has('bases') && SP_Config_Configurator::get('app')->use_database)
		{
			SP_Database_BaseCollector::fromConfiguration(SP_Config_Configurator::get('bases'));
		}
	}
	
	public function getModule()
	{
		return $this->module;
	}
	
	public function getAction()
	{
		return $this->action;
	}
	
	public function getRouteVars()
	{
		return $this->route_vars;
	}
	
	//возвращает путь к шаблону части
	private function getPartViewPath($name)
	{
		return SP_Config_Configurator::getIncludeDir().$this->app->getPartsDir().$name.'.php';
	}
	
	//возвращает путь к контроллеру части
	private function getPartControllerPath()
	{
		return SP_Config_Configurator::getIncludeDir().$this->app->getPartsDir().$this->part_controller_name;
	}
	
	//возвращает путь к папке текущего или указанного модуля
	private function getModulePath($module=null)
	{
		if(!$module) $module=$this->getModule();
		return $this->app->getModulesDir().$module.'/';
	}
	
	private function getModuleRouteConfig($module=null)
	{
		if(!$module) $module=$this->getModule();
		return SP_Config_Configurator::get('route')->get('route_'.$module);
	}
	
	public function getModuleConfig()
	{
		return SP_Config_Configurator::get('module');
	}
	
	//возвращает путь к шаблону текущего или указанного экшена для текущего или указанного модуля 
	private function getActionPath($action=null,$module=null)
	{
		if(!$action) $action=$this->getAction();
		return $this->getModulePath($module).$action.'.php';
	}
	
	//возвращает путь к файлу-контроллеру текущего или указанного модуля
	private function getModuleControllerPath($module=null)
	{
		return $this->getModulePath($module).$this->module_controller_name;
	}
	
	//проверка существования текущего модуля
	private function checkModule($module=NULL)
	{
		return (is_file(SP_Config_Configurator::getIncludeDir().$this->getModuleControllerPath($module)) &&  is_readable(SP_Config_Configurator::getIncludeDir().$this->getModuleControllerPath($module)));
	}
	
	//проверка существования шаблона текущего или указанного экшена для текущего или указанного модуля
	private function checkActionFile($action=null,$module=null)
	{
		return (is_file(SP_Config_Configurator::getIncludeDir().$this->getActionPath($action,$module)) &&  is_readable(SP_Config_Configurator::getIncludeDir().$this->getActionPath($action,$module)));
	}
	
	//проверка существования метода текущего или указанного экшена для текущего или указанного модуля
	private function checkActionMethod($action=null,$module=null)
	{
		if($this->checkModule($module))
		{
			require_once(SP_Config_Configurator::getIncludeDir().$this->getModuleControllerPath($module));
			if(!$module) $module=$this->getModule();
			$class='Controller'.$module;
			if(!$action) $action=$this->getAction();
			$method='start'.$action;
			return method_exists($class, $method);
		}
	}

	private function define404()
	{
		$this->module='404';
		$this->action='index';
		$route=SP_Routing_RouteCollector::get( SP_Config_Configurator::get('app')->route_name_for_404 );
		if( isset($route) )
		{
			$this->module=$route->getModule();
			if( empty($this->module) or $this->module=='' ) throw new exception('404 module is undefined');
			
			if( $action=$route->getAction() )
			{
				$this->action=$action;
			}
			elseif($action=SP_Config_Configurator::get('app')->default_action)
			{
				$this->action=$action;
			}
		}
		if( !$this->checkModule() ) throw new exception('404 module not found');
		if( !$this->checkActionMethod() ) throw new exception('404 action not found');
	}
	
	private function setModule($module)
	{
		if( empty($module) or $module=='' ) throw new exception('Can not set empty module');
		$this->module=$module;
		if($this->checkModule())
		{
			SP_Config_Configurator::readConfig('module',$this->getModulePath().$this->module_config_file);
			return true;
		}
		return false;
	}
	
	private function setAction($action)
	{
		if( empty($action) or $action=='' )
		{
			$action=$this->getModuleConfig()->default_action;
			if( empty($action) or $action=='' )
			{
				$action=SP_Config_Configurator::get('app')->default_action;
				if( empty($action) or $action=='' )
				{
					$action='index';
				}
			}
		}
		$this->action=$action;
		return $this->checkActionMethod();
	}
	
	//определяет маршрут, устанавливает модуль и действие
	public function compileRoute($uri=null)
	{
		$router = new SP_Routing_Router;
		$router->setUri($uri);
		if( false === $route_name=$router->defineRoute() )
		{
			$this->define404();
			return;
		}
		$this->current_route_name=$route_name;

		$this->is_home_page=$router->isRootPage();
		if( !$this->setModule($router->getModule()) )
		{
			$this->define404();
			return;
		}

		if( !$this->setAction($router->getAction()) )
		{
			$this->define404();
			return;
		}

		$this->route_vars=$router->getVars();
		//var_dump($this->route_vars);
	}
	
	public function getReplacedFrom()
	{
		return $this->replaced_from;
	}
	
	public function replace($route_to_go, $params=array())
	{
		unset($this->module);
		unset($this->action);
		unset($this->route_vars);
		$this->cycle_prevent++;
		if($this->cycle_prevent > 10) throw new Exception('To much replaces');
		$this->replaced_from_route_name=$this->current_route_name;
		$this->replaced_from=array($this->getModule(),$this->getAction(),$this->getRouteVars());

		if(strpos($route_to_go,':') === false)
		{
			if(is_array($route_to_go))
			{
				if( !$this->setModule($route_to_go['module']) ) throw new Exception('Can not replace to "'.$route_to_go['module'].'", module not exist');
				if( !$this->setAction($route_to_go['action']) ) throw new Exception('Can not replace to "'.$route_to_go['action'].'", action not exist');
				$this->route_vars=$route_to_go;
			}
			else
			{
				$this->route_to_go=$route_to_go;
			}
		}
		else
		{
			$route_name=substr($route_to_go, 1);
			if(!SP_Routing_RouteCollector::has($route_name)) throw new Exception('Can not replace to route "'.$route_name.'", route not found');
			$this->current_route_name=$route_name;
			$route=SP_Routing_RouteCollector::get($route_name);
			if( !$this->setModule($route->getModule()) ) throw new Exception('Can not replace to route "'.$route_name.'", module "'.$this->module.'" not exist');
			if( !$this->setAction($route->getAction()) ) throw new Exception('Can not replace to route "'.$route_name.'", action "'.$this->action.'" not exist');
			$this->route_vars=$params;
			//echo $this->module.' '.$this->action.'<br />';
		}
		throw new Exception('replace',self::REPLACE_CODE);
	}
	
	public function redirect($uri,$external=false)
	{
		if( SP_Config_Configurator::get('app')->show_script_name and !$external )
		{
			$uri=SP_Config_Configurator::get('app')->script_name.$uri;
		}
		throw new Exception($uri,self::REDIRECT_CODE);
	}

	public function replace404()
	{
		$this->define404();
		throw new Exception($url,self::REDIRECT_CODE);
	}

	public function replace404Unless($value)
	{
		if(!$value) $this->replace404();
	}

	public function replace404If($value)
	{
		if($value) $this->replace404();
	}

	public function render()
	{
		if( empty($this->module) or empty($this->action) )
		{
			$this->compileRoute($this->route_to_go);
			unset($this->route_to_go);
		}
		//echo $this->module.' '.$this->action.'<br />';
		require_once(SP_Config_Configurator::getIncludeDir().$this->getModuleControllerPath());
		$class='Controller'.$this->getModule();
		$method='start'.$this->getAction();
		$controller = new $class;
		$controller->setRouteVars($this->route_vars);
		$before_method='beforeStart';
		$after_method='afterStart';
		
		try
		{
			if(method_exists($class, $before_method)) $controller->$before_method($this);
			
			$controller->$method($this);
			
			if(method_exists($class, $after_method)) $controller->$after_method($this);
		}
		catch(Exception $e) 
		{
			if($e->getCode() == self::REPLACE_CODE)
			{
				$this->render();
				return;
			}
			elseif($e->getCode() == self::REDIRECT_CODE)
			{
				Header("Location: ".$e->getMessage());
			}
			else
			{
				die($e);
			}
		}
		
		
		$this->controller_vars=$controller->getVars();
		
		$this->renderContent();
		if($this->use_template)
		{
			$this->defineCss();
			$this->defineJs();
			$this->sendHeader();
			$this->renderTemplate();
			$this->echoPage();
		}
		else
		{
			echo $this->content;
		}
	}
	
	private function echoPage()
	{
		echo $this->page_content;
	}
	
	private function sendHeader()
	{
		header("Content-Type: text/html;charset=utf-8");
	}
	
	public function setTemplateVar($name,$value)
	{
		$this->template_vars[$name]=$value;
	}
	
	public function hasTemplateVar($name)
	{
		return isset($this->template_vars[$name]);
	}
	
	public function getTemplateVar($name,$value=null)
	{
		return $this->hasTemplateVar($name)? $this->template_vars[$name] : $value;
	}
	
	private function renderTemplate()
	{
		if(empty($this->template))
		{
			$this->template=SP_Config_Configurator::get('app')->template;
			$module_conf=SP_Config_Configurator::get('module');
			if($module_conf)
				if($module_conf->template) $this->setTemplate($module_conf->template);
		}
		$_template_=$this->getTemplatePath();
		if(!(is_file($_template_) && is_readable($_template_))) throw new exception(sprintf("%s template not found",$this->template));
		foreach($this->template_vars as $_key_ => $_value_)
		{
			$$_key_=$_value_;
		}
		ob_start();
		include($_template_);
		$this->page_content=ob_get_contents();
		ob_end_clean();
	}
	
	private function renderContent()
	{
		foreach($this->controller_vars as $_key_ => $_value_)
		{
			$$_key_=$_value_;
		}
		ob_start();
		if( !$this->checkActionFile() ) throw new exception('Template for "'.$this->action.'" action not found');
		include(SP_Config_Configurator::getIncludeDir().$this->getActionPath());
		$this->content=ob_get_contents();
		ob_end_clean();
	}
	
	private function content()
	{
		echo $this->content;
	}
	
	/***PARTS***/
	
	public function partExist($name)
	{
		$part_view=$this->getPartViewPath($name);
		return (is_file($part_view) && is_readable($part_view) && is_readable($this->getPartControllerPath()));
	}
	
	public function partMethodExist($name)
	{
		$part_controller=$this->getPartControllerPath();
		if(is_readable($part_controller))
		{
			require_once($part_controller);
			$class = new PartController;
			$method='start'.$name;
			return method_exists($class, $method);
		}
		return false;
	}
	

	public function renderPart($name)
	{
		if($this->partExist($name))
		{
			if($this->partMethodExist($name))
			{
				$part = new PartController;
				$method='start'.$name;
				$part->$method();
				$_PART_VARS=$part->getVars();
				unset($part);
				unset($method);
			}
			foreach($_PART_VARS as $key => $value)
			{
				$$key=$value;
			}
			ob_start();
			include($this->getPartViewPath($name));
			$this->part_content[$name]=ob_get_contents();
			ob_end_clean();
		}
	}
	
	public function includePart($name)
	{
		$this->renderPart($name);
		echo $this->part_content[$name];
	}
	

	/***SLOTS***/
	
	public function setSlot($name,$slot)
	{
		$this->slots[$name]=$slot;
	}
	
	public function hasSlot($name)
	{
		return isset($this->slots[$name]);
	}
	
	public function slot($name,$alt=NULL)
	{
		if ($this->hasSlot($name))
		{
			return $this->slots[$name];
		}
		else
		{
			return $alt;
		}
	}
	
	/***IN TEPLATE USE***/
	
	public function isHomePage()
	{
		return $this->is_home_page;
	}
	
	public function setTemplate($name)
	{
		$this->template=$name;
	}
	
	private function getTemplatePath()
	{
		return SP_Config_Configurator::getIncludeDir().$this->app->getTemplatesDir().$this->template.'.php';
	}
	
	public function useTemplate($value)
	{
		$this->use_template=(bool)$value;
	}
	
	public function setCssDir($dir)
	{
		$this->css_dir=$dir;
	}
	
	public function getCssDir()
	{
		return '/'.$this->css_dir.'/';
	}
	
	public function setCss($css_array)
	{
		$this->clearCss();
		foreach($css_array as $name)
			$this->includeCss($name);
	}
	
	private function defineCss()
	{
		$this->setCss(SP_Config_Configurator::get('app')->get('css',array()));
		$module_conf=$this->getModuleConfig();
		if(is_array($module_conf->css)) 
		{
			foreach($module_conf['css'] as $css)
				$this->includeCss($name);
		}
	}
	
	public function includeCss($name)
	{
		$this->css[$name]=1;
	}
	
	public function excludeCss($name)
	{
		unset($this->css[$name]);
	}
	
	public function clearCss()
	{
		$this->css=array();
	}
	
	public function echoCss()
	{
		foreach($this->css as $css => $value)
		{
			echo '<link href="'.$this->getCssDir().$css.'.css" rel="stylesheet" type="text/css">'."\n";
		}
	}
	
	public function setJsDir($dir)
	{
		$this->js_dir=$dir;
	}
	
	public function getJsDir()
	{
		return '/'.$this->js_dir.'/';
	}
	
	public function setJs($js_array)
	{
		$this->clearJs();
		foreach($js_array as $name)
			$this->includeJs($name);
	}
	
	private function defineJs()
	{
		$this->setJs(SP_Config_Configurator::get('app')->get('js',array()));
		$module_conf=$this->getModuleConfig();
		if(is_array($module_conf->js)) 
		{
			foreach($module_conf['js'] as $js)
				$this->includeJs($name);
		}
	}
	
	public function includeJs($name)
	{
		$this->js[$name]=1;
	}
	
	public function excludeJs($name)
	{
		unset($this->js[$name]);
	}
	
	public function clearJs()
	{
		$this->js=array();
	}
	
	public function echoJs()
	{
		foreach($this->js as $js => $value)
		{
			echo '<script src="'.$this->getJsDir().$js.'.js" type="text/javascript"></script>'."\n";
		}
	}
	
}